<?php
$GLOBALS['DB']	=	new Db();

class Db
{
	protected $driver;
	protected $table;
	
	protected $stuff	=	array();
	protected $last	=	'';
	protected $hkey	=	null;

	protected static $pool	=	array();
	
	
	/**
	 * get instance of db
	 * 
	 * @return Db
	 */
	static function get($model)
	{
		static $stack	=	array();
		static $cursor	=	null;
		
		if($model!=$cursor)
		{
			if($cursor)
			{
				$stack[$cursor]	=	get_object_vars($GLOBALS['DB']);
			}
			
			if(isset($stack[$model]))
			{
				foreach($stack[$model] as $k=>&$v)
				{
					$GLOBALS['DB']->$k	=	$v;
				}
			}
			else
			{
				$cfg	=	Lib::config('db');
				$GLOBALS['DB']->driver	=	self::loadDriver(isset($cfg[$model]) ? $cfg[$model] : $cfg['default']);
				$GLOBALS['DB']->table	=	self::table($model);
				$GLOBALS['DB']->stuff	=	array();
				$GLOBALS['DB']->last	=	'';
			}
			
			$cursor	=	$model;
		}
		
		return $GLOBALS['DB'];
	}
	
	static function table($model)
	{
		static $hash	=	array();
		
		if(is_object($model))
		{
			$model	=	substr(get_class($model), 0, -5);
		}
		if(!isset($hash[$model]))
		{
			$tables	=	Lib::config('table');
			$hash[$model]	=	Core::cfg('prefix') . (isset($tables[$model]) ? $tables[$model] : strtolower($model));
		}
		
		return $hash[$model];
	}
	
	/**
	 * todo: mysql有一个问题，使用相同用户名密码做两次连接且选择不同数据库时，只会产生一个实际连接，
	 * 		 这是mysql_connect的机制(说有办法解决，但感觉不好)，尽量避免这种情况
	 */
	static function loadDriver($mydns)
	{
		if(!isset(self::$pool[$mydns]))
		{
			$cfg	=	parse_url($mydns);
			$model	=	'DbDriver'.ucfirst($cfg['scheme']);
			import('#/model/'.$model.'.php');
			self::$pool[$mydns]	=	call_user_method_array('instance', $model, array(
										$cfg['host'], $cfg['user'], $cfg['pass'], substr($cfg['path'],1), 
										!empty($cfg['query']) && false!==strpos($cfg['query'], 'persist')
									));
		}
		return self::$pool[$mydns];
	}
	
	/**
	 * 关闭所有数据库链接（如果需要）
	 */
	static function close()
	{
		if(self::$pool)
		{
			foreach(self::$pool as $ins)
			{
				$ins->close();
			}
			self::$pool	=	array();
		}
	}
	
	//---------------------SELECT-------------------------
	
	/**
	 * 
	 * @return Db
	 */
	function select($fields='*')
	{
		is_array($fields) ? $fields=join(',',$fields) : null;
		$this->stuff['case']	=	'select';
		$this->stuff['fields']	=	$fields;
		return $this;
	}
	function getOne($res=null)
	{
		return $this->driver->fetchOne(($res===null) ? $this->exec() : $res);
	}
	function getCol()
	{
		try{
			return array_pop($this->driver->fetchOne($this->exec()));
		}
		catch(Exception $_){return false;}
	}
	function getCols()
	{
		$all	=	$this->getAll();
		if($all)
		{
			$tmp	=	array();
			foreach($all as $a)
			{
				$tmp[]	=	array_shift($a);
			}
			return $tmp;
		}
		return null;
	}
	function getAll($res=null)
	{
		if($this->hkey)
		{
			$list	=	($res===null)
						? $this->driver->fetchAll($this->exec())
						: $this->driver->fetchAll($res);
			if($list)
			{
				$tmp	=	array();
				foreach($list as $r)
				{
					$tmp[$r[$this->hkey]]	=	$r;
				}
				$this->hkey	=	null;
				return $tmp;
			}
			$this->hkey	=	null;
			return false;
		}
		else
		{
			return ($res===null)
					? $this->driver->fetchAll($this->exec())
					: $this->driver->fetchAll($res);
		}
	}

	/**
	 * 
	 * @return Db
	 */
	public function limit($s, $l=null)
	{
		if(!$s && !$l)
		{
			return $this;
		}
		if($l===null)
		{
			$this->stuff['limit']	=	$this->driver->makeLimit(0, $s);
		}
		else
		{
			$this->stuff['limit']	=	$this->driver->makeLimit($s, $l);
		}
		return $this;
	}
	/**
	 * 
	 * @return Db
	 */
	public function group($group)
	{
		$this->stuff['group']	=	$group;
		return $this;
	}
	/**
	 * 
	 * @return Db
	 */
	public function order($order)
	{
		$this->stuff['order']	=	$order;
		return $this;
	}
	
	/**
	 * 做为返回结果集hash的key的字段
	 * 
	 * @return Db
	 */
	public function hkey($key)
	{
		if(array_key_exists($key, $this->fields()))
		{
			$this->hkey	=	$key;
			return $this;
		}
		else
		{
			throw new Exception("From hkey: $key is not in table {$this->table}");
		}
	}

	/**
	 * 
	 * @return Db
	 */
	function count($field='*')
	{
		return $this->select('COUNT('.$field.')');
	}
	
	//---------------------other commands-------------------------
	
	/**
	 * 
	 * @return Db
	 */
	function insert($data)
	{
		if(!$data) return false;
		$this->stuff['case']	=	'insert';
		$this->stuff['fields']	=	$data;
		return $this;
	}
	
	/**
	 * 
	 * @return Db
	 */
	function update($data)
	{
		if(!$data) return false;
		$this->stuff['case']	=	'update';
		$this->stuff['fields']	=	$data;
		return $this;
	}

	/**
	 * 
	 * @return Db
	 */
	function delete()
	{
		$this->stuff['case']	=	'delete';
		return $this;
	}
	
	/**
	 * 
	 * @return Db
	 */
	function replace($data)
	{
		if(!$data) return false;
		$this->stuff['case']	=	'replace';
		$this->stuff['fields']	=	$data;
		return $this;
	}
	
	/**
	 * 
	 * @return Db
	 */
	public function where()
	{
		$this->stuff['where']	=	$this->toCondition(func_get_args(), true);
		return $this;
	}
	
	
	//---------------------QUERY-------------------------
	
	function exec()
	{
		$sql	=	   '';
		switch($this->stuff['case'])
		{
		case 'select':
			$sql	=	"SELECT {$this->stuff['fields']} FROM {$this->table}";
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['group']) ? null : $sql.=(' GROUP BY '.$this->stuff['group']);
			empty($this->stuff['order']) ? null : $sql.=(' ORDER BY '.$this->stuff['order']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
			
		case 'insert':
			$sql	=	'INSERT INTO '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			break;
			
		case 'replace':
			$sql	=	'REPLACE '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			break;
			
		case 'update':
			$sql	=	'UPDATE '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
			
		case 'delete':
			$sql	=	'DELETE FROM '.$this->table;
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			//empty($this->stuff['order']) ? null : $sql.=(' ORDER BY '.$this->stuff['order']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
		}
		
		$this->stuff	=	array();
		$this->last		=	$sql;
		return $this->driver->query($sql);
	}
	
	function query($sql)
	{
		if(false!==strpos($sql, '$TABLE'))
		{
			$sql	=	str_replace('$TABLE', $this->table, $sql);
		}
		$this->last	=	$sql;
		return $this->driver->query($sql);;
	}
	
	
	//----------------------util--------------------------

	function fields()
	{
		static $fields	=	array();
		if(!isset($fields[$this->table]))
		{
			try
			{
				$fields[$this->table]	=	import(DATA_PATH.'/cache/fields_'.$this->table.'.php');
			}
			catch(Exception $e)
			{
				$fields[$this->table]	=	$this->driver->fields($this->table);
				if(!Core::cfg('debug'))
				{
					file_put_contents(
						DATA_PATH."/cache/fields_{$this->table}.php",
						'<'."?php\nreturn ".var_export($fields[$this->table], true).";\n?".'>'
						);
				}
			}
		}
		return $fields[$this->table];
	}
	
	function validMap($data)
	{
		$tmp	=	array();
		$map	=	$this->fields();
		foreach($data as $k=>$v)
		{
			if(isset($map[$k])) switch($map[$k])
			{
			case 0:	//string
				$tmp[$k]	=	$this->driver->qstr($v);
				break;
			case 1:	//iteger
				$tmp[$k]	=	intval($v);
				break;
			case 2:	//float
				$tmp[$k]	=	floatval($v);
				break;
			}
		}
		return $tmp;
	}
	
	function buildRequest($data, $sepr=',')
	{
		$qu		=	$this->driver->qu();
		$s		=	array();
		$data	=	$this->validMap($data);
		foreach($data as $k=>$d)
		{
			$s[]	=	"{$qu}{$k}{$qu}='{$d}'";
		}
		return join($sepr, $s);
	}
	
	/**
	 * 生成SQL条件部分的方法，支持以下形式的参数:
	 *   1.常规值判断表达式
	 *     array('a'=>'b','c'=>'d') <=> SQL: 'a="b" and c="d"'
	 *   2.SQL语法表达式
	 *     array('a','=','b'), array('c','=','d') <=> SQL: 'a=b and c=d'
	 *     array('e','is', 'null')  <=> SQL: 'e is null'
	 *     array('f','=','""')      <=> SQL: 'f=""'
	 *   3.直接写SQL
	 *     a!=b and c=d				<=> SQL: 'a!=b and c=d'
	 */
	function toCondition($ary, $_bewith=false)
	{
		if($_bewith)
		{
			$t	=	array();
			foreach($ary as $a)
			{
				if($a)
				$t[]	=	$this->toCondition($a);
			}
			return $t ? join(' AND ', $t) : '';
		}
		elseif(!$ary)
		{
			return '';
		}
		elseif(count($ary)===3 && is_int(key($ary)))
		{
			return join(' ', $ary);
		}
		elseif(is_string($ary))
		{
			return $ary;
		}
		else
		{
			if(isset($ary['_pk']))
			{
				$ary[self::pk()]	=   $ary['_pk'];
				unset($ary['_pk']);
			}
			return $this->buildRequest($ary, ' AND ');
		}
	}
	
	//-------------------------------helper methods------------------------------
	
	function pk()
	{
		$o  =   $this->fields();
		return isset($o['_pk']) ? $o['_pk'] : null;
	}

	function insertId()
	{
		return $this->driver->insertId();
	}
	function affectedRows()
	{
		return $this->driver->affectedRows();
	}
	function lastError()
	{
		return $this->driver->lastError();
	}
	function lastSql()
	{
		return $this->last;
	}
	
	function qstr($str)
	{
		return addslashes($str);
	}
	
	function increase($cond, $field, $step=1)
	{
		$this->update(array($field,'=',"{$field}+{$step}"))->where($cond)->exec();
		return $this->affectedRows();
	}
	function decrease($cond, $field, $step=1)
	{
		$this->update(array($field,'=',"{$field}-{$step}"))->where($cond)->exec();
		return $this->affectedRows();
	}
	
	
}
?>
